# Copyright 2020 Efabless Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import click
import xlsxwriter

@click.command()
@click.option(
    '--benchmark', '-b', 'benchmark_file', required=True,
    help="The csv file from which to extract the benchmark results"
)
@click.option(
    '--full-benchmark/--no-full-benchmark', default=True,
    help="If no, use the regression results instead of the benchmark as the design list, i.e., don't look for *every* design in the benchmark."
)
@click.option(
    '--output-report', '-o', 'output_report_file', required=True, 
    help="The file to print the final report in"
)
@click.option(
    '--output-xlsx', '-x', 'output_xlsx_file', required=False,
    help="The csv file to print a merged csv file benchmark vs regression_script in"
)
@click.argument('regression_results_file')
def cli(benchmark_file, full_benchmark, output_report_file, output_xlsx_file, regression_results_file):
    not_full_benchmark = not full_benchmark

    benchmark = dict()
    regression_results = dict()

    output_report_list = []

    testFail = False

    configuration_mismatches = []
    critical_mismatches = []

    missing_configs = []


    base_configs = ['CLOCK_PERIOD', 'SYNTH_STRATEGY', 'SYNTH_MAX_FANOUT','FP_CORE_UTIL', 'FP_ASPECT_RATIO',
                    'FP_PDN_VPITCH', 'FP_PDN_HPITCH', 'PL_TARGET_DENSITY', 'GLB_RT_ADJUSTMENT', 'STD_CELL_LIBRARY', 'CELL_PAD', 'DIODE_INSERTION_STRATEGY']

    tolerance = {'general_tolerance':1, 'tritonRoute_violations':2, 'Magic_violations':10, 'antenna_violations':10,'lvs_total_errors':0}

    critical_statistics = ['tritonRoute_violations','Magic_violations',  'antenna_violations','lvs_total_errors']

    note_worthy_statistics = []

    ignore_list = ['', 'design', 'design_name', 'config']

    def compare_vals(benchmark_value, regression_value,param):
        if str(benchmark_value) == "-1":
            return True
        if str(regression_value) == "-1":
            return False
        tol = 0-tolerance['general_tolerance']
        if param in tolerance.keys():
            tol = 0-tolerance[param]
        if float(benchmark_value) - float(regression_value) >= tol:
            return True
        else:
            return False

    def findIdx(header, label):
        for idx in range(len(header)):
            if label == header[idx]:
                return idx
        else:
            return -1

    def diff_list(l1, l2):
        return [x for x in l1 if x not in l2]

    def parseCSV(csv_file, isBenchmark):
        nonlocal note_worthy_statistics
        map_out = dict()
        csvOpener = open(csv_file, 'r')
        csvData = csvOpener.read().split("\n")
        headerInfo = csvData[0].split(",")
        designNameIdx = findIdx(headerInfo, "design")
        if isBenchmark:
            note_worthy_statistics=diff_list(diff_list(diff_list(headerInfo,ignore_list),critical_statistics),base_configs)

        remover = 0
        size = len(base_configs)
        while remover < size:
            if base_configs[remover] not in headerInfo:
                missing_configs.append(base_configs[remover])
                base_configs.pop(remover)
                remover -= 1
                size -= 1
            remover += 1

        if designNameIdx == -1:
            print("Invalid report. No design name was found.")
            exit(-1)
        for i in range(1, len(csvData)):
            if len(csvData[i]):
                entry = csvData[i].split(",")
                designName=entry[designNameIdx]
                for idx in range(len(headerInfo)):
                    if idx != designNameIdx:
                        if designName not in map_out.keys():
                            map_out[designName] = dict()
                            if isBenchmark:
                                map_out[designName]["Status"] = "PASSED"
                            else:
                                map_out[designName]["Status"] = "----"
                        map_out[designName][headerInfo[idx]] = str(entry[idx])
        return map_out


    def configurationMismatch(benchmark, regression_results):
        nonlocal configuration_mismatches
        designList = list()
        if not_full_benchmark:
            designList = regression_results.keys()
        else:
            designList = benchmark.keys()

        for design in designList:
            output_report_list.append("\nComparing Configurations for: "+ design+"\n")
            configuration_mismatches.append("\nComparing Configurations for: "+ design+"\n")
            if design not in regression_results:
                output_report_list.append("\tDesign "+ design+" Not Found in the provided regression sheet\n")
                configuration_mismatches.append("\tDesign "+ design+" Not Found in the provided regression sheet\n")
                continue

            if design not in benchmark:
                output_report_list.append("\tDesign "+ design+" Not Found in the provided benchmark sheet\n")
                configuration_mismatches.append("\tDesign "+ design+" Not Found in the provided benchmark sheet\n")
                continue

            size_before = len(configuration_mismatches)
            for config in base_configs:
                if benchmark[design][config] == regression_results[design][config]:
                    output_report_list.append("\tConfiguration "+ config+" MATCH\n")
                    output_report_list.append("\t\tConfiguration "+ config+" value: "+ benchmark[design][config] +"\n")
                else:
                    configuration_mismatches.append("\tConfiguration "+ config+" MISMATCH\n")
                    output_report_list.append("\tConfiguration "+ config+" MISMATCH\n")
                    configuration_mismatches.append("\t\tDesign "+ design + " Configuration "+ config+" BENCHMARK value: "+ benchmark[design][config] +"\n")
                    output_report_list.append("\t\tDesign "+ design + " Configuration "+ config+" BENCHMARK value: "+ benchmark[design][config] +"\n")
                    configuration_mismatches.append("\t\tDesign "+ design + " Configuration "+ config+" USER value: "+ regression_results[design][config] +"\n")
                    output_report_list.append("\t\tDesign "+ design + " Configuration "+ config+" USER value: "+ regression_results[design][config] +"\n")
            if size_before == len(configuration_mismatches):
                configuration_mismatches=configuration_mismatches[:-1]

    def criticalMistmatch(benchmark, regression_results):
        nonlocal testFail
        nonlocal critical_mismatches
        designList = list()
        if not_full_benchmark:
            designList = regression_results.keys()
        else:
            designList = benchmark.keys()

        for design in designList:
            output_report_list.append("\nComparing Critical Statistics for: "+ design+"\n")
            critical_mismatches.append("\nComparing Critical Statistics for: "+ design+"\n")
            if design not in regression_results:
                testFail = True
                benchmark[design]["Status"] = "NOT FOUND"
                output_report_list.append("\tDesign "+ design+" Not Found in the provided regression sheet\n")
                critical_mismatches.append("\tDesign "+ design+" Not Found in the provided regression sheet\n")
                continue

            if design not in benchmark:
                testFail = False
                output_report_list.append("\tDesign "+ design+" Not Found in the provided benchmark sheet\n")
                critical_mismatches.append("\tDesign "+ design+" Not Found in the provided benchmark sheet\n")
                continue

            size_before = len(critical_mismatches)
            for stat in critical_statistics:
                if compare_vals(benchmark[design][stat],regression_results[design][stat],stat):
                    output_report_list.append("\tStatistic "+ stat+" MATCH\n")
                    output_report_list.append("\t\tStatistic "+ stat+" value: "+ benchmark[design][stat] +"\n")
                else:
                    testFail = True
                    benchmark[design]["Status"] = "FAIL"
                    critical_mismatches.append("\tStatistic "+ stat+" MISMATCH\n")
                    output_report_list.append("\tStatistic "+ stat+" MISMATCH\n")
                    critical_mismatches.append("\t\tDesign "+ design + " Statistic "+ stat+" BENCHMARK value: "+ benchmark[design][stat] +"\n")
                    output_report_list.append("\t\tDesign "+ design + " Statistic "+ stat+" BENCHMARK value: "+ benchmark[design][stat] +"\n")
                    critical_mismatches.append("\t\tDesign "+ design + " Statistic "+ stat+" USER value: "+ regression_results[design][stat] +"\n")
                    output_report_list.append("\t\tDesign "+ design + " Statistic "+ stat+" USER value: "+ regression_results[design][stat] +"\n")
            if len(critical_mismatches) == size_before:
                critical_mismatches= critical_mismatches[:-1]

    def noteWorthyMismatch(benchmark, regression_results):
        designList = list()
        if not_full_benchmark:
            designList = regression_results.keys()
        else:
            designList = benchmark.keys()

        for design in designList:
            output_report_list.append("\nComparing Note Worthy Statistics for: "+ design+"\n")
            if design not in regression_results:
                output_report_list.append("\tDesign "+ design+" Not Found in the provided regression sheet\n")
                continue

            if design not in benchmark:
                output_report_list.append("\tDesign "+ design+" Not Found in the provided benchmark sheet\n")
                continue

            for stat in note_worthy_statistics:
                if benchmark[design][stat] == regression_results[design][stat] or benchmark[design][stat] == "-1":
                    output_report_list.append("\tStatistic "+ stat+" MATCH\n")
                    output_report_list.append("\t\tStatistic "+ stat+" value: "+ benchmark[design][stat] +"\n")
                else:
                    output_report_list.append("\tStatistic "+ stat+" MISMATCH\n")
                    output_report_list.append("\t\tDesign "+ design + " Statistic "+ stat+" BENCHMARK value: "+ benchmark[design][stat] +"\n")
                    output_report_list.append("\t\tDesign "+ design + " Statistic "+ stat+" USER value: "+ regression_results[design][stat] +"\n")

    flow_failures = []
    def flowFailures(regression_results):
        nonlocal flow_failures
        for design in regression_results.keys():
            if "failed" in regression_results[design]["flow_status"]:
                flow_failures.append(design)

    benchmark = parseCSV(benchmark_file,1)
    regression_results = parseCSV(regression_results_file,0)
    flowFailures(regression_results)
    configurationMismatch(benchmark, regression_results)
    criticalMistmatch(benchmark, regression_results)
    noteWorthyMismatch(benchmark, regression_results)

    with open(output_report_file, 'w') as report_file:
        write = lambda *args, **kwargs: print(*args, **kwargs, file=report_file)
        if testFail:
            write("Failed")
        else:
            write("Passed")
        write("---")

        if len(flow_failures):
            write("[OpenLane Flow Failures] (Designs where OpenLane failed outright)")
            write("")
            for failure in flow_failures:
                write(failure)
            write("")
            write("---")

        if len(missing_configs):
            write("[Missing Configuration Variables]  (Do not exist in the sheets.)")
            write("")
            for variable in missing_configs:
                write(variable)
            write("")
            write("---")

        if testFail:
            write("[Critical Mismatches] (Cause Failures)")
            write("".join(critical_mismatches))
            write("---")

        if testFail and len(configuration_mismatches):
            write("[Configuration Mismatches] (May Contribute To Differences Between Results)")
            write("".join(configuration_mismatches))
            write("---")

        write("[Full Report]")
        write("".join(output_report_list))

    def formNotFoundStatus(benchmark, regression_results):
        for design in benchmark.keys():
                if design not in regression_results:
                    benchmark[design]["Status"] = "NOT FOUND"

    formNotFoundStatus(benchmark, regression_results)

    if output_xlsx_file is None:
        return

    # Open an Excel workbook
    workbook = xlsxwriter.Workbook(output_xlsx_file)

    # Set up a format
    fail_format = workbook.add_format(properties={'bold': True, 'font_color': 'red'})
    pass_format = workbook.add_format(properties={'bold': True, 'font_color': 'green'})
    diff_format = workbook.add_format(properties={'font_color': 'blue'})
    header_format = workbook.add_format(properties={'font_color': 'gray'})
    benchmark_format = workbook.add_format(properties={'bold': True, 'font_color': 'navy'})

    # Create a sheet
    worksheet = workbook.add_worksheet('diff')

    headerInfo = ['Owner','design', 'Status']
    headerInfo.extend(critical_statistics)
    headerInfo.extend(note_worthy_statistics)
    headerInfo.extend(base_configs)

    # Write the headers
    for col_num, header in enumerate(headerInfo):
        worksheet.write(0,col_num, header,header_format)

    # Save the data from the OrderedDict into the excel sheet
    idx = 0
    while idx < len(benchmark):
        worksheet.write(idx*2+1, 0, "Benchmark", benchmark_format)
        worksheet.write(idx*2+2, 0, "User")

        design = str(list(benchmark.keys())[idx])
        if design not in regression_results:
            for col_num, header in enumerate(headerInfo):
                if header == 'Owner':
                    worksheet.write(idx*2+1, col_num, "Benchmark", benchmark_format)
                    worksheet.write(idx*2+2, col_num, "User")
                    continue
                if header == 'design':
                    worksheet.write(idx*2+1, col_num, design)
                    worksheet.write(idx*2+2, col_num, design)
                    continue
                worksheet.write(idx*2+1, col_num, benchmark[design][header])
        else:
            for col_num, header in enumerate(headerInfo):
                if header == 'Owner':
                    worksheet.write(idx*2+1, col_num, "Benchmark", benchmark_format)
                    worksheet.write(idx*2+2, col_num, "User")
                    continue
                if header == 'design':
                    worksheet.write(idx*2+1, col_num, design)
                    worksheet.write(idx*2+2, col_num, design)
                    continue
                if header == 'Status':
                    if benchmark[design][header] == "PASSED":
                        worksheet.write(idx*2+1, col_num, benchmark[design][header],pass_format)
                        worksheet.write(idx*2+2, col_num, regression_results[design][header],pass_format)
                    else:
                        worksheet.write(idx*2+1, col_num, benchmark[design][header],fail_format)
                        worksheet.write(idx*2+2, col_num, regression_results[design][header],fail_format)
                    continue
                if benchmark[design][header] != regression_results[design][header]:
                    if header in critical_statistics:
                        if compare_vals(benchmark[design][header],regression_results[design][header],header) == False:
                            worksheet.write(idx*2+1, col_num, benchmark[design][header],fail_format)
                            worksheet.write(idx*2+2, col_num, regression_results[design][header],fail_format)
                        else:
                            worksheet.write(idx*2+1, col_num, benchmark[design][header],pass_format)
                            worksheet.write(idx*2+2, col_num, regression_results[design][header],pass_format)
                    else:
                        worksheet.write(idx*2+1, col_num, benchmark[design][header],diff_format)
                        worksheet.write(idx*2+2, col_num, regression_results[design][header],diff_format)
                else:
                    worksheet.write(idx*2+1, col_num, benchmark[design][header])
                    worksheet.write(idx*2+2, col_num, regression_results[design][header])
        idx+=1


    # Close the workbook
    workbook.close()

if __name__ == '__main__':
    cli()